References (C++98)

  • C has pointers only.

  • C++ has both pointers and references.

  • A reference is an alias for an existing object.

  • References were added in C++ because many uses of pointers are not actually about optional objects or ownership; they're simply about avoiding copies while guaranteeing that an object exists.

int x = 42;
int& ref = x;

ref = 10;

printf("%d\n", x); // 10
  • Once initialized:

    • it must refer to an object

    • it cannot be reseated to refer to another object

    • you use it with normal object syntax ( ref.foo()  instead of ptr->foo() )

  • Cannot be reseated :

    int a = 1;
    int b = 2;
    
    int& r = a;
    r = b;    // copies b into a
    
    • Result:

      a == 2
      b == 2
      
      • And r  still refers to a .

  • Can't store "empty" references :

    int& r;   // error
    
    • You must initialize a reference immediately.

    • Pointers don't have this restriction.

  • Const References const T& :

    • See the 'Functions' section.

If I want null safety while having indirection, i can't pass a pointer, as the pointer can be null, I have to pass a pointer to an object, and therefore, i have to ensure that i have an object first, to then take its pointer.
so foo(ptr) is not allowed, but foo(&a) would be, considering that a is a valid object (not a pointer)
so cpp enforces this by making foo(a) being the only allowed syntax, and disallowing pointers

Positives

  • Suppose you want a function that modifies an object.

  • With pointers:

    void increment(int* x) {
        (*x)++;
    }
    
    increment(&value);
    
  • With references:

    void increment(int& x) {
        x++;
    }
    
    increment(value);
    
  • The reference version is:

    • cleaner

    • impossible to accidentally pass nullptr

    • clearly expresses "this parameter is required"

  • Rule of thumb:

    • Use references when an object must exist.

    • Use pointers when the object is optional, may change, or pointer semantics are required.

Negatives

  • References can still dangle :

    • This is probably the biggest misconception.

    • References prevent null references, not dangling references.

    int& bad() {
        int x = 42;
        return x;      // dangling reference
    }
    
    • This compiles, but using the returned reference is undefined behavior.

  • Not explicit at call site :

    • foo(a)  doesn't tell me if a can be mutated inside foo.

    • In C:

      foo(&a);
      
      • Seeing &a  immediately tells you the function receives an address.

    • In C++:

      foo(a);
      
      • You need to know whether the signature is

        void foo(Object);
        void foo(const Object&);
        void foo(Object&);
        void foo(Object&&);
        
      • Those all have identical call syntax.

      • So C++ deliberately traded explicitness at the call site for cleaner syntax.

How it works under the hood

  • References are mostly syntax sugar

  • A reference is often implemented internally as a pointer, but the language gives it stronger guarantees.

  • These two are conceptually similar:

void f(int& x);
  • And

void f(int* x);
  • Except the reference version guarantees:

    • x  exists

    • x  isn't null

In other low-level languages

  • Rust :

    • Rust has explicit references:

    fn increment(x: &mut i32)
    
    • Rust references are much stronger than C++ references because the borrow checker enforces that they never dangle and that aliasing rules are respected at compile time.

  • Carbon :

    • Intentionally adopts C++-like references

  • Vale :

    • Has reference semantics with ownership and regions

  • Many language designers consider C++ references to be in an awkward middle ground.

    • They eliminate a few mistakes:

      • no null

      • no reseating

    • But they don't solve the hardest problems:

      • dangling references

      • lifetime management

      • aliasing

    • So if a language wants memory safety, it often goes much further (like Rust). If it doesn't, it usually sticks with plain pointers because they're simpler and more explicit.